/*
   (C) 2005 - Michael Ocean - Boston University

  This work is a part of the larger snBench project.  Information
  can be found at: http://csr.bu.edu/snbench

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package sxe;

import http.NanoHTTPD.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import snbenchlib.Log;
import snbenchlib.Trace;
import snbenchlib.Util;
import step.DanglingSplice;
import step.Graph;
import sxe.NameSpace;
import snbenchlib.XML;
import snobject.snCommand;
import snobject.snString;
import snobject.snInteger;
import snobject.snObject;
import sxe.core.hash.SQLClientHelper;
import sxe.core.hash.SQLClientInstance;
import sxe.sensors.GenericSensor;
import sxe.sensors.SensorDetector;

/**
 * This is the glue that holds the SXE together.  It interfaces the
 * STEP graph component with the HTTP server component.
 *
 * Note that requests are handled in threads.  Be afraid.
 */
public class Server extends http.ResourceHandler
{
	public static final int DEFAULT_RESOURCE_SHARE = 100;
	public static final String versionID = "sxe-java/0.20050712";
	private static final String ARG_KEY_LOCAL_URI = "localuri";
	private static final String ARG_KEY_SSD_URI = "ssduri";
	private static final String ARG_KEY_SXE_INFO_DEBUG = "sxeinfo";
	private static final String ARG_KEY_STEP_GRAPH = "stepfile";
	private static final String ARG_KEY_GUI = "debuggui";
	
	public static String SD_DEFAULT = "http://resourcemonitor.bu.edu:8079";
	
	public String markupProxyBase = 
		null;
	public String XMLStylePath = 
		"/snbench/markup/";
	public static String XMLStyleSheet =
		"sxe20050712.xsl";
	
	public String XMLStyle() {
		if (XMLStylePath != null)
			return "<?xml-stylesheet href=\"" + XMLStylePath + XMLStyleSheet + "\"" + 
				" type=\"text/xsl\" media=\"screen\" ?>\n";
		else
			return "";
	}

	/* For use by the applet */
	protected void SetStylesheetPath(String z) {
		XMLStylePath = z;
	}
	protected void SetMarkupProxyBase(String z) {		markupProxyBase = z;
	}
	
	public	String 						ID;
	private step.Graph 					LiveGraph;
	private sxe.GraphEvaluatorThread 	LiveEvaluator;
	private NodeJanitor 				sxeNodeJanitor;
	private sxe.Gui						guiDebugUI;
	
	private http.Server 				httpServer;

	private URI 						serviceDispatcher;

	private URI 						self;
	private boolean 					bShutDownInitiated = false;

	
	private void init(int iPort, boolean bShowGui) throws IOException{
		if(bShowGui){
			guiDebugUI = new sxe.Gui(LiveGraph);
			Log.InitLogs(guiDebugUI);
		}
		else{
			guiDebugUI = null;
			Log.InitLogs(null);
		}
		
		LiveEvaluator = new GraphEvaluatorThread(LiveGraph,guiDebugUI);
		sxeNodeJanitor = new NodeJanitor(LiveGraph,LiveEvaluator);
		
		if(!SQLClientHelper.IsConfigured()){
			Log.server.log("JDBC/MYSQL jar file is not present.  Hash/Storage Opcodes will be unavailable.");
		}
		
		SensorDetector.Detect();
		
		if(iPort==0)
			httpServer = http.Server.GetServer(versionID);
		else
			httpServer = http.Server.GetServer(versionID,iPort);
		httpServer.RegisterHandler(this);
	
		// create an array list of the hosted sensors for the ManagedSXE
		ArrayList<GenericSensor> sensors = SensorDetector.getSensors(GenericSensor.TYPE_ALL);

		if(serviceDispatcher==null){
			Log.server.log("SD communication is disabled" +
					"--this SXE is running in stand alone mode.");
		}

		SxeMonitor.create(ID, self, serviceDispatcher, DEFAULT_RESOURCE_SHARE, sensors);
		
	}
	
	protected void start() {
		LiveEvaluator.start();
		sxeNodeJanitor.start();
		
		if(serviceDispatcher!=null)
			SxeMonitor.Start();
	}

	protected void stop() {
		/* oredering here is tricky, as race conditions abound. */
		Log.server.log("Shutting down, have a nice day...");
		
		preShutdown(); // - disable some URIs 
		
		Log.server.log("Shutting down NodeJanitor...");
		sxeNodeJanitor.Shutdown();
		
		boolean bPrintOnce = false;
		while(!sxeNodeJanitor.isDone()) {
			if(!bPrintOnce){
				Log.server.log("..still waiting on NodeJanitor");
				bPrintOnce = true;
			}
		}
		Log.server.log("[OK]");
		
		
		
		/* Stop the evaluator */
		Log.server.log("Shutting down STEP Evaluator...");
		bPrintOnce = false;
		LiveEvaluator.Shutdown();
		while (!LiveEvaluator.isDone()) {
			if(!bPrintOnce){
				Log.server.log("..still waiting on STEP Evaluator");
				bPrintOnce = true;
			}
		}
		Log.server.log("[OK]");
		
		/* now the evaluator has stopped */
			
		/* notify the SD, wait for ack */
		Log.server.log("Shutting down SXE Monitor...");
		bPrintOnce = false;
		SxeMonitor.Shutdown();

		Log.server.log("[OK]");
		
		/* throw an interrupt at the server thread */
		//s.shutdown();
		// do {} while (!s.isDone());
	}

	/**
	 * call when the shutdown process has begun.  Disable
	 * all URIs and indicate that the server is going down.
	 *
	 */
	private void preShutdown() {
		bShutDownInitiated  = true;
	}

	private Response serveRoot(String method,
				   Properties header,
				   Properties parms,
				   Properties accept) {
		if (method.equals("GET") ||
		    method.equals("HEAD")) {
			if (http.Server.Acceptable(accept, "text/xml") > 0.0) {
				LiveGraph.getReadLock();
				String body = XML.XMLHead + XMLStyle() + 
					"<sxe xmlns=\"" + XML.XMLNS + "\" id=\"" +
					ID + "\" resources=\"" + SxeMonitor.getAvailableResources() + "\">\n" + 
					"  <step id=\"" + 
					LiveGraph.GetID() + 
					"\" href=\"/snbench/sxe/node/\"/>\n";
				LiveGraph.releaseReadLock();
				
				body += "</sxe>\n";
				return httpServer.OK("text/xml", body);
			} else {
				return httpServer.NotAcceptable("text/xml");
			}
		} else {
			return httpServer.MethodNotAllowed("GET, HEAD");
		}
	}

	private Response graphGet(Properties accept) {
		if (http.Server.Acceptable(accept, "text/xml") > 0.0) {
			LiveGraph.getReadLock();
			String body = XML.XMLHead + XMLStyle() + 
				"<step id=\"" + LiveGraph.GetID() + "\" xmlns=\"" + XML.XMLNS + "\">\n";
			
			step.Node n = null;
			Iterator<step.Node> it = LiveGraph.GetNodeIterator();
			while(it.hasNext()){
	    		n = it.next();
	    		
				body += "  <" + n.TagName() + 
					" id=\"" + n.GetID() + "\"" + 
					" href=\"/snbench/sxe/node/" + n.GetID() + 
					"/\" />\n";
			}
			LiveGraph.releaseReadLock();
			body += "</step>\n";
			return httpServer.OK("text/xml", body);
		} else
			return httpServer.NotAcceptable("text/xml");
	}

	// SJJ - SMOD: Added to return the current graph
	private Response getSXEGraph(Properties accept) {
		if (http.Server.Acceptable(accept, "text/xml") > 0.0) {
			String body = LiveGraph.Serialize();
			//return httpServer.OK(MimeTypes.STEPPROGRAM, body);
			return httpServer.OK("text/xml", body);
		} else
			return httpServer.NotAcceptable("text/xml");
	}
	

	private Response ReturnSnObject(snObject z,
		     Properties header,
		     Properties accept) {

		if (z != null) {
			java.io.ByteArrayOutputStream baos = 
				new java.io.ByteArrayOutputStream();
			try {
				if (http.Server.Acceptable(accept, z.getXmlMimeType()) > 0.1) {
					z.xmlWrite(baos,0);
					return httpServer.NoCache(httpServer.OK(z.getXmlMimeType(), baos.toString()));
				} else if (http.Server.Acceptable(accept, z.getWebMimeType()) > 0.1) {
					z.webWrite(baos,0);
					/* MJO:  baos.toString() doesn't work for binary data... */
					ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
					return httpServer.NoCache(httpServer.OK(z.getWebMimeType(), bais));
				} else if (http.Server.Acceptable(accept, z.getBinaryMimeType()) > 0.1) {
					z.binaryWrite(baos);
					return httpServer.NoCache(httpServer.OK(z.getBinaryMimeType(), baos.toString()));
				} else {
					return httpServer.NotAcceptable(
						z.getXmlMimeType() + ", " +
						z.getWebMimeType() + ", " +
						z.getBinaryMimeType());
				}
			} catch (java.io.IOException ex) {
				return httpServer.InternalServerError(
					"Serialization Error: " + 
					ex.toString());
			}
		} else
			return httpServer.ServiceUnavailable(
				"This node currently has no value.  " + 
				"Try again after it has computed.");
	}
	
	private Response GetNodeData(step.Node n,
				     Properties header,
				     Properties accept) {
		snObject z = n.GetValue();
		return ReturnSnObject(z, header, accept);
	}

	private Response GetNodeInfo(step.Node n) {
		return httpServer.OK("text/xml",
				     (XML.XMLHead + XMLStyle() + 
				      n.GetInfoXML()));
	}


	private Response serveGraph(String method,
				    Properties header,
				    Properties parms,
				    byte [] body,
				    Properties accept) {
		if (method.equals("POST")) {
			if (http.Server.Acceptable(accept, "text/xml") > 0.0) {
				String ContentType = header.getProperty(
					"Content-Type", "application/binary");
				if (ContentType.equals(snobject.MimeTypes.STEPPROGRAM) ||
				    ContentType.equals("text/xml") ||
				    ContentType.equals("application/xml")) {
					Log.server.log("Post GOT GRAPH:" + new String(body), Trace.MINIMAL);
					step.Graph newg = step.parse.Parser.Parse(new String(body),false);
					if(newg==null){
						return httpServer.createResponse(
								http.Server.HTTP_CONFLICT,
								snCommand.FAILED.getAcceptableMimeType(accept), 
								"Parsing of the given STEP program failed.");
					}
					try {
						/* Actually graft the new graph onto the existing graph
						 * Will throw an exception in the case of failure.
						 */
						GraftGraph(newg);
					} catch (step.DanglingSplice e) {
						Log.server.log("Graph could not be grafted due to dangling splice");
						Log.server.printStackTrace(e);
						return httpServer.createResponse(
							http.Server.HTTP_CONFLICT,
							snCommand.FAILED.getAcceptableMimeType(accept), 
							snCommand.FAILED.getAcceptable(accept));
					} catch (ClassNotFoundException e) {
						Log.server.log("Graph could not be grafted due to unavailable Opcode: " + e.getMessage());
						Log.server.printStackTrace(e);
						return httpServer.createResponse(
							http.Server.HTTP_CONFLICT,
							snCommand.FAILED.getAcceptableMimeType(accept), 
							snCommand.FAILED.getAcceptable(accept));
					}
					return httpServer.Created();
				} else{
					Log.server.log(":POST of STEP program failed -- program was an Usupported Media Type [" + ContentType + "]");
					return httpServer.UnsupportedMediaType("This resource accepts POSTs of snbench/stepprogram, text/xml, and application/xml types ONLY.");
				}
			} else
				return httpServer.NotAcceptable("text/xml");
		} else if (method.equals("GET") ||
			   method.equals("HEAD")) {
			return graphGet(accept);
		} else {
			return httpServer.MethodNotAllowed("GET, HEAD, POST");
		}
	}

	protected void GraftGraph(Graph newg) throws DanglingSplice, ClassNotFoundException {
		LiveGraph.Graft(newg);
		
		LiveGraph.getReadLock();
		LiveEvaluator.updateSchedueForNew();
		LiveGraph.releaseReadLock();
		
		int resources = Graph.ComputeNeeds(newg);
		Log.server.log("Decrementing resources by " + resources);
		SxeMonitor.decrAvailResources( resources );
		//System.err.println(SxeMonitor.getXML());		
	}

	private Response serveNode(step.Node n,
				   String uri,
				   String method,
				   Properties header,
				   Properties parms,
				   byte [] body,
				   Properties accept) {
		if (n==null)
			return httpServer.NotFound();
		if (uri.equals("")) {
			if (method.equals("POST")) {
				snObject obj;
				try {
					obj = snObject.Parse(header.getProperty("Content-Type"),
							     body);
				} catch (snobject.MimeException ex) {
					return httpServer.UnsupportedMediaType("Must POST a recognized (usually <tt>snbench/*</tt>) media type to this resource");
				} catch (step.parse.SyntaxError ex) {
					return httpServer.BadRequest("Syntax error in POSTed object: " + ex.getMessage());
				}
				if (obj.getXmlMimeType().equals(snobject.MimeTypes.COMMAND)) {
					try {
						n.ReceiveCommand((snCommand) obj);
					} catch (ClassCastException ex) {
						return httpServer.BadRequest("Request content-type was snbench/command, but it did not parse as such");
					} catch (snobject.UnknownCommand ex) {
						return httpServer.BadRequest(ex.getMessage());
					} catch (snobject.InvalidCommand ex) {
						return httpServer.BadRequest(ex.getMessage());
					} catch (step.EvaluationFailure ex) {
						return httpServer.InternalServerError(ex.getMessage());
					}
					return httpServer.Accepted(
						"text/xml",
						(XML.XMLHead + XMLStyle() +
						 "<results xmlns=\"" + XML.XMLNS + "\">" +
						 ("<accepted href=\"/snbench/sxe/node/" + 
						  n.GetID() + "/\"/>") +
						 "</results>\n"));
				} else {
					step.ReceiverPostSocketNode rpn;
					try {
						rpn = ((step.ReceiverPostSocketNode) n);
					} catch (ClassCastException ex) {
						return httpServer.Forbidden("POST forbidden to non-socket node");
					}
					rpn.POST(obj);
					return httpServer.Accepted();
				}
			} else if (method.equals("DELETE")) {
				LiveGraph.getWriteLock();
				LiveGraph.markForDeletion(n);
				LiveGraph.releaseWriteLock();
			
				return httpServer.OK(snCommand.OK.getXmlMimeType(),
						new String(snCommand.OK.getXml()));

			} else if (method.equals("GET") ||
				   method.equals("HEAD")) {
				if (http.Server.Acceptable(accept, "text/xml") > 0.0) {
					return httpServer.OK(
						"text/xml",
						(XML.XMLHead + XMLStyle() + 
						 "<node id=\"" + n.GetID() + "\"" + 
						 " xmlns=\"" + XML.XMLNS + "\">\n" +
						 "  <info href=\"info\"/>\n" + 
						 "  <data href=\"data\"/>\n" +
						 "</node>\n"));
				} else
					return httpServer.NotAcceptable("text/xml");
			} else {
				return httpServer.MethodNotAllowed("GET, HEAD, POST, DELETE");
			}
		} else if (uri.equals("data")) {
			if (method.equals("GET") ||
			    method.equals("HEAD"))
				return GetNodeData(n, header, accept);
			else 
				return httpServer.MethodNotAllowed("GET, HEAD");
		} else if (uri.equals("info")) {
			if (method.equals("GET") ||
			    method.equals("HEAD"))
				return GetNodeInfo(n);
			else
				return httpServer.MethodNotAllowed("GET, HEAD");
		} else {
			return httpServer.NotFound(uri);
		}
	}
	

	private Response serveSensorTypes(String method,
		      Properties header,
		      Properties parms,
		      byte [] body,
		      Properties accept) {
			if (method.equals("GET") ||
			  method.equals("HEAD")) {
				return httpServer.OK("text/xml",
						     (XML.XMLHead + XMLStyle() + 
						    	SensorDetector.getSensorTypesXML()));
				
			} else {
				return httpServer.MethodNotAllowed("GET, HEAD");
			}
		}

	/**
	 * Modified 9/06 to support SQL based hash server instead of the previous
	 * proprietary server
	 * 
	 * @param method
	 * @param header
	 * @param parms
	 * @param body
	 * @param accept
	 * @return
	 */
	private Response serveStorage(String method,
		      Properties header,
		      Properties parms,
		      byte [] body,
		      Properties accept) {
		//REQUIRES: method,header,parms,body,accept forwarded from http server code
		//Modifies: SnHashStorage Singleton class
		//Effects: Parses the parms http request info and applies appropriate actions to
		//         the SnHashStorage class.
			
			SQLClientHelper sqli;
			
			try{
				sqli = SQLClientInstance.getInstance();
			}catch(SQLException sqe){
				return httpServer.InternalServerError(sqe.getMessage());				
			}
			
			
			if(!parms.isEmpty()) {
		    	//If there are any parameters we accept only keys {action,value,key}
		    	//action must be "put" or "remove", if not given defaults to "put"
		    	//If action = put, it will put a pair (key,value)
		    	//If action = remove, it will remove the pair defined by key's value
	
		    	String actionType="put";  //default action
		    	String putKey="";
		    	String putValue="";
		    	//loop through http parameters and set up our variables
		    	for(Map.Entry<Object,Object> e : parms.entrySet()) {
		    		//Only allowed to set Integers and Strings from webpage
		    		String key=(String)e.getKey();
		    		if(key.equals("key")){
		    			putKey=(String)e.getValue();
		    		}else if(key.equals("value")){
		    			putValue=(String)e.getValue();
		    		}else if(key.equals("action")){
		    			actionType=(String)e.getValue();
		    		}
		    	}
		    	//only accept put,remove,get
		    	try{
		    		if(actionType.equals("put")){
			    		try {
			    			sqli.Put(new snInteger(Integer.valueOf(putValue)), putKey);
			    		}catch(NumberFormatException ex){
			    			//We're assuming string key
			    			sqli.Put(new snString(putValue), putKey);
			    		}
			    	}else if(actionType.equals("remove")){
			    		sqli.Remove(putKey);
			    	}else if(actionType.equals("get")){
			    		snObject o = sqli.Get(putKey);
			    	}
		    	}
		    	catch(SQLException sqe){
		    		return httpServer.BadRequest(sqe.getMessage());
		    	}
			}
		    //Return the storage's representation of itself to the browser
		    if (method.equals("GET") ||
			  method.equals("HEAD")) {
				return httpServer.OK("text/xml",
						     (XML.XMLHead + XMLStyle() + 
						    	sqli.getXML()));
		    }
			else {
				return httpServer.MethodNotAllowed("GET, HEAD");
			}
		
	
	}

	/**
	 * Return a particular object from the hash table to the caller
	 * 
	 * @param entry_name
	 * @param method
	 * @param header
	 * @param parms
	 * @param body
	 * @param accept
	 * @return
	 */
	private Response serveStorageObject(String entry_name,
			  String method,
		      Properties header,
		      Properties parms,
		      byte [] body,
		      Properties accept) {
		
			SQLClientHelper sqli; 
			try{
				sqli = SQLClientInstance.getInstance();
			}catch(SQLException sqe){
				return httpServer.InternalServerError(sqe.getMessage());				
			}


			// Only accept GET/HEAD requests on specific hash entries
			if (method.equals("GET") ||
			  method.equals("HEAD")) {
		    	
		    	snObject z;
				try {
					z = sqli.Get(entry_name);
				} catch (SQLException e) {
					e.printStackTrace();
					return httpServer.NoCache(httpServer.BadRequest(e.getMessage()));
				} 
				return ReturnSnObject(z, header, accept);		    	

		    }
			else {
				return httpServer.MethodNotAllowed("GET, HEAD");
			}
		
	
	}

	
	private Response serveSensors(String method,
				      Properties header,
				      Properties parms,
				      byte [] body,
				      Properties accept,
				      String type) {
		if (method.equals("GET") ||
		    method.equals("HEAD")) {
			return httpServer.OK("text/xml",
					     (XML.XMLHead + XMLStyle() + 
					    	SensorDetector.getSensorsXML(type)));
			
		} else {
			return httpServer.MethodNotAllowed("GET, HEAD");
		}
	}	
	private Response serveSensor(GenericSensor sensor,
				     String util,
				     String method,
				     Properties header,
				     Properties parms,
				     byte [] body,
				     Properties accept) {
		if (method.equals("GET") ||
		    method.equals("HEAD")) {
			return httpServer.OK("text/xml",
					     (XML.XMLHead + XMLStyle() +
					      sensor.GetInfoXML()));
		} else {
			return httpServer.MethodNotAllowed("GET, HEAD: " + method);
		}
	}

	private Response serveGraphApplet(
		     String method,
		     Properties accept) {
		if (http.Server.Acceptable(accept, "text/xml") > 0.0) {
			if (method.equals("GET") ||
			   method.equals("HEAD")) {
			return httpServer.OK("text/xml",
					     (XML.XMLHead + XMLStyle() +
					    	"<applet host=\"" + self.toString() + "\" " +
					    			"xmlns=\"" + XML.XMLNS + "\"/>"
					     ));
			} else {
				return httpServer.MethodNotAllowed("GET, HEAD: " + method);
			}
		}
		return httpServer.NotAcceptable("text/xml");
	}
	
	
	public Response serve(String uri,
			      String method,
			      Properties header,
			      Properties parms,
			      byte [] body) {
		
		Log.server.log("Serving [" + uri + "] " + header + "||" + parms);

		if(bShutDownInitiated){
			return httpServer.InternalServerError("Sorry, the server is currently shutting down.   Please try back again later.");
		}
		
		Properties accept = http.Server.BuildAcceptTable(header.getProperty("Accept"));

		Response resp = null;
		if (uri.equals("/applet.html")){	
			try{
				resp = httpServer.NoCache(serveGraphApplet(method, accept));
			}catch (Exception ex) {
				return httpServer.InternalServerError(ex.getMessage());
			}
		}else if (uri.equals("/tmp.html")){
			try{
				resp = httpServer.NoCache(
						  httpServer.serveFile(
								"tmp.html", header,
								new java.io.File("http/files"),
								false));
						
			}catch (Exception ex) {
				return httpServer.InternalServerError(ex.getMessage());
			}
		}else if (uri.equals("/snbench/sxe/graphview/")){
			try{
				resp = httpServer.NoCache(
						  httpServer.serveFile(
								"graphview.html", header,
								new java.io.File("http/files"),
								false));
						
			}catch (Exception ex) {
				return httpServer.InternalServerError(ex.getMessage());
			}
			
		}else if (uri.endsWith(".jar")||uri.endsWith(".class")) {
			try {
				resp = 
				//	httpServer.NoCache(
						httpServer.serveFile(
							uri.substring(uri.lastIndexOf("/")), header,
							new java.io.File("http/files"),
							false);
			}catch (Exception ex) {
				return httpServer.InternalServerError(ex.getMessage());
			}
		}else if (uri.equals(NameSpace.Malformed(NameSpace.ROOT))) {
			resp = httpServer.Bounce(NameSpace.ROOT);
		} else if ((uri.length() < 9) ||
			   !uri.substring(0, 9).equals(NameSpace.ROOT)) {
			return null;
		} else if (uri.equals(NameSpace.ADMIN)){
			try{
				resp = httpServer.NoCache(
						httpServer.serveFile(
								"sxe_admin.html", header,
								new java.io.File("http/"),
								false));
			}catch (Exception ex) {
				return httpServer.InternalServerError(ex.getMessage());
			}
		} else if ((uri.length()>=16) && 
			   uri.substring(0, 16).equals(NameSpace.MARKUP)) {
			try {
				resp = 
					httpServer.NoCache(
						httpServer.serveFile(
							uri.substring(16), header,
							new java.io.File("markup/"),
							false));
			} catch (Exception ex) {
				return httpServer.InternalServerError(ex.getMessage());
			}
		} else if (uri.equals(NameSpace.Malformed(NameSpace.SXE))) {
			resp = httpServer.Bounce(NameSpace.SXE);
		} else if (uri.equals(NameSpace.SXE)) {
			resp = httpServer.NoCache(serveRoot(method, header, parms, accept));
		} else if (uri.equals(NameSpace.Malformed(NameSpace.SXE_NODE))) {
			resp = httpServer.Bounce(NameSpace.SXE_NODE);
		} else if (uri.equals(NameSpace.SXE_NODE)) {
			resp = httpServer.NoCache(serveGraph(method, header, parms, body, accept));
		} else if (uri.equals(NameSpace.Malformed(NameSpace.SXE_GRAPH))) {
			resp = httpServer.Bounce(NameSpace.SXE_GRAPH);
		}else if (uri.equals(NameSpace.SXE_GRAPH)) {
			// SJJ - SMOD: Added this command to retrieve the current running graph
			resp = httpServer.NoCache(getSXEGraph(accept));
		} 
		else if ((uri.length()>=18) && 
			   uri.substring(0, 18).equals(NameSpace.SXE_NODE)) {
			String uril = uri.substring(18);
			String[] parts = uril.split("/");
			if ((parts.length == 1) &&
			    (!uril.endsWith("/")))
				return httpServer.Bounce(NameSpace.SXE_NODE + parts[0] + "/");
			uril = uril.substring(parts[0].length() + 1);
			if(uril==null){
				return httpServer.BadRequest(uril);
			}
			if(uril.endsWith("/")){
				return httpServer.Bounce(uri.substring(0,uri.length()-1));
			}
			step.Node n = LiveGraph.GetNode(parts[0]);
			if (n == null)
				return httpServer.NotFound();
			resp = httpServer.NoCache(
				serveNode(n, uril, 
					  method, header, parms, body, accept));
		} else if (uri.equals(NameSpace.Malformed(NameSpace.SXE_SENSOR))) {
			resp = httpServer.Bounce(NameSpace.SXE_SENSOR);
		} else if (uri.equals(NameSpace.SXE_SENSOR)) {
			resp = httpServer.NoCache(serveSensorTypes(method, header, parms, body, accept));
		} else if (uri.equals(NameSpace.SXE_STORAGE)) {
			//If snbench/sxe/storage is requested call serveStorage to apply any actions
			//described by parameters and to display the storage's state to the browser
			resp = httpServer.NoCache(serveStorage(method, header, parms, body, accept));
		} else if (uri.startsWith(NameSpace.SXE_STORAGE)) {
			/* Caller is requesting a particular piece of data from the hash */
			String s[] = Util.URISplitter(uri, NameSpace.SXE_STORAGE);
			if(s==null)
				resp = httpServer.NoCache(httpServer.NotFound());
			else if(s.length==1)
				resp = httpServer.NoCache(serveStorageObject(s[0],method, header, parms, body, accept));
			else
				resp = httpServer.NoCache(httpServer.NotFound());
		} else if ((uri.length()>=20) &&
			   uri.substring(0, 20).equals(NameSpace.SXE_SENSOR)) {
			String uril = uri.substring(20);
			String[] parts = uril.split("/");
			if (parts.length == 1){
				if(!uril.endsWith("/"))
					return httpServer.Bounce(NameSpace.SXE_SENSOR + parts[0] + "/");
				uril = uril.substring(parts[0].length() + 1);
				
				/* look up sensor type */
				if(SensorDetector.validateSensorType(parts[0]))
					resp = httpServer.NoCache(serveSensors(method, header, 
							parms, body, accept, parts[0]));
				else
					return httpServer.NotFound();	
			}
			else if(parts.length == 2){
				if(!uril.endsWith("/"))
					return httpServer.Bounce(NameSpace.SXE_SENSOR + parts[0] + "/" + parts[1] + "/");
				/* look up sensor type */
				if(!SensorDetector.validateSensorType(parts[0]))
					return httpServer.NotFound();
				GenericSensor gs = SensorDetector.GetDevice(parts[1],parts[0]);

				if (gs == null)
					return httpServer.NotFound();
				resp = httpServer.NoCache(
					serveSensor(gs, uril,
						    method, header, parms, body, accept));	
			}
			else if(parts.length == 3){
				if(!uril.endsWith("/"))
					return httpServer.Bounce(NameSpace.SXE_SENSOR + parts[0] + "/" + parts[1] + "/" + parts[2] + "/");
				if(!"data".equals(parts[2]))
					return httpServer.NotFound();
				
				/* look up sensor type */
				if(!SensorDetector.validateSensorType(parts[0]))
					return httpServer.NotFound();
				GenericSensor gs = SensorDetector.GetDevice(parts[1],parts[0]);

				if (gs == null)
					return httpServer.NotFound();
				resp = httpServer.NoCache(
					serveSensorData(gs, uril,
						    method, header, parms, body, accept));	
			}
			else
				return httpServer.NotFound();
		} else {
			return null;
		}
		return resp;
	}

	private Response serveSensorData(GenericSensor gs, String uril, String method, Properties header, Properties parms, byte[] body, Properties accept) {
		if (method.equals("GET") ||
			    method.equals("HEAD")) {
			// TODO: parse parms
			return ReturnSnObject(gs.getData(null), header, accept);			
		} else {
				return httpServer.MethodNotAllowed("GET, HEAD: " + method);
		}
	}

	private class Shutdown extends Thread {
		public Shutdown(Server _server) {
			server = _server;
		}
		protected Server server;

		public void run() {
			server.stop();
		}
	}

	private Shutdown sd = null;
	public Shutdown getShutdown(Server s) {
		if (sd == null)
			sd = new Shutdown(s);
		return sd;
	}

	/**
	 * prints usage followed by the additional message and quits
	 * 
	 * @param additionalMsg
	 */
	static public void usage(String additionalMsg) {
		System.err.println(
			"Usage:\nsxe.Server --" + ARG_KEY_LOCAL_URI + " <uri> [--" + ARG_KEY_SSD_URI + " <ssd_uri>] [--" + ARG_KEY_STEP_GRAPH + " <stepfile>] [--" + ARG_KEY_SXE_INFO_DEBUG + "] \n" +
			"  <uri>  -  the local host's URI (address/hostname and desired port) -e.g., \"http://hostname:port\"\n" +
			"  <ssd_uri>  - the URI of the Service Dispatcher that will control this SXE -e.g., \"http://hostname:port\"\n" +
			"             - may specify NONE to specify no stand alone operation or omit for default\n" +
			"  <stepfile> - filename/URI of a STEP program to be run immediately after launch\n" +
			"  --" + ARG_KEY_SXE_INFO_DEBUG + " - prints the XML heartbeat for this SXE and quits immediately.");
		System.err.println("\n\n" + additionalMsg);
		System.exit(-1);
	}	
	
	protected Server(URI uLocalHost, URI uriSSD, boolean bShowGui) throws Exception {
		assert(uLocalHost.getScheme().equals("http"));
		ID = uLocalHost.getScheme() + "://" + uLocalHost.getRawAuthority() ;
		XML.THISID = ID;
				 
		// initialize the graph for use
		try {
			java.util.Date d = new java.util.Date();
			LiveGraph = step.Graph.newEmptyGraph(
				String.valueOf(d.getTime()) + 
				":" + uLocalHost.toString());			

			LiveGraph.getWriteLock();
			LiveGraph.ResolveSplices();
			LiveGraph.Normalize();
			LiveGraph.Bind();
			LiveGraph.releaseWriteLock();
		} catch (Exception ex) {
			ex.printStackTrace();
			System.exit(-1);
		}

		serviceDispatcher = uriSSD;
		self = uLocalHost;
		init(uLocalHost.getPort(),bShowGui);
	}
	
	static public void main(String args[]) {
		step.Graph g = null;
		URI uSSD, uLocalHost;
		Object o = null;
		boolean bShowGui = false;
		
		if (args.length == 0) {
			usage("No arguments specified.");
		}
		
		Hashtable<String,Object> htArgs = Util.parseArgs(args);
		if(!htArgs.containsKey(ARG_KEY_LOCAL_URI)){
			usage("Local host URI must be specified.");
		}
		o = htArgs.get(ARG_KEY_LOCAL_URI);
		if(!(o instanceof String))
			usage("Invalid URI specified for local host.");

		String sUri = (String)o;
		try{
			uLocalHost = new java.net.URI(sUri);
			if(!uLocalHost.isAbsolute())
				throw new URISyntaxException(sUri,"Local host URI specified was invalid.  URI must be absolute (e.g., http://...)");
		}catch (java.net.URISyntaxException ex) {
			System.err.println(ex.getMessage());
			return;
		}		
				
		if(htArgs.containsKey(ARG_KEY_SSD_URI)){
			o = htArgs.get(ARG_KEY_SSD_URI);
			if(!(o instanceof String)){
				usage("Invalid URI specified for SSD.  Must be a valid URI or NONE.");
			}
			String ssdUri = (String)o;
			
			if(ssdUri.equalsIgnoreCase("none"))
				uSSD=null;
			else{
				// process the specified URI
				try{
					uSSD = new java.net.URI(ssdUri);
					if(!uSSD.isAbsolute())
						throw new URISyntaxException(ssdUri,"SSD URI specified was invalid.  URI must be absolute (e.g., http://...)");
				}catch (java.net.URISyntaxException ex) {
					System.err.println(ex.getMessage());
					return;
				}
			}
		}
		else{
			System.out.println("Using default service dispatcher: " + SD_DEFAULT);
			uSSD = java.net.URI.create(SD_DEFAULT);
		}		
		
		if(htArgs.containsKey(ARG_KEY_GUI))
			bShowGui = true;
		
		server_instance = null;
		try {
			server_instance = new Server(
                uLocalHost, 
				uSSD,
				bShowGui);
		} catch (Exception ex) {
			ex.printStackTrace();
			System.exit(-1);			
		}
		
		if(htArgs.containsKey(ARG_KEY_STEP_GRAPH)){
			o = htArgs.get(ARG_KEY_STEP_GRAPH);
			if(o instanceof String){
				String sFile = (String)o;
				System.out.println("Loading STEP graph specified : " + sFile);
				g = step.parse.Parser.Parse(new java.io.File(sFile),false);
				if(g==null)
					System.exit(-1);
				System.out.println("Grafting STEP graph specified : " + sFile);

				try {
					server_instance.GraftGraph(g);
				} catch (DanglingSplice e) {
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
			else{
				usage("Invalid STEP file specified.");
			}
		}		
		if(htArgs.containsKey(ARG_KEY_SXE_INFO_DEBUG)){
			SxeMonitor.PrintSXEInfo();
			System.exit(0);
		}
		
		Runtime.getRuntime().addShutdownHook(server_instance.getShutdown(server_instance));

		// invoke services/other threads
		server_instance.start();

		return;
	}

	/**
	 * the static instance of the SXE server (created by main)
	 */
	private static Server server_instance = null;
	
	/**
	 * @return - the URI to the SSD (Service Dispatcher) that the current 
	 * SXE instance is communicating with as 
	 */
	public static URI GetServiceDispatcher() {
		return server_instance.serviceDispatcher;
	}
}
